/*
 * Copyright (c) 2018, apexes.net. All rights reserved.
 *
 *         http://www.apexes.net
 *
 */
package net.apexes.commons.lang;

import java.util.HashSet;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;

/**
 * 一致性hash算法
 *
 * @author <a href=mailto:hedyn@foxmail.com>HeDYn</a>
 */
public class ConsistenthashHelper {

    private final int virtualCount;
    private final SortedMap<Integer, String> virtualNodes;
    private final Set<String> sourceNodes;

    public ConsistenthashHelper() {
        this(200);
    }

    public ConsistenthashHelper(int virtualCount) {
        this.virtualCount = virtualCount;
        this.virtualNodes = new TreeMap<>();
        this.sourceNodes = new HashSet<>();
    }

    public boolean isEmpty() {
        return sourceNodes.isEmpty();
    }

    public synchronized void clear() {
        sourceNodes.clear();
        virtualNodes.clear();
    }

    public synchronized Set<String> nodesSnapshoot() {
        return new HashSet<>(sourceNodes);
    }

    public synchronized void add(String node) {
        if (!sourceNodes.contains(node)) {
            sourceNodes.add(node);
            for (int i = 0; i < virtualCount; i++) {
                virtualNodes.put(hash(node, i), node);
            }
        }
    }

    public synchronized void remove(String node) {
        sourceNodes.remove(node);
        for (int i = 0; i < virtualCount; i++) {
            virtualNodes.remove(hash(node, i));
        }
    }

    public synchronized String get(String key) {
        if (virtualNodes.isEmpty()) {
            return null;
        }
        int hash = hashcode(key);
        String target = virtualNodes.get(hash);
        if (target == null) {
            SortedMap<Integer, String> subMap = virtualNodes.tailMap(hash);
            hash = subMap.isEmpty() ? virtualNodes.firstKey() : subMap.firstKey();
            target = virtualNodes.get(hash);
        }
        return target;
    }

    private int hash(String node, int index) {
        String name = node + "&&VN" + String.valueOf(index);
        return hashcode(name);
    }

    private int hashcode(String key) {
        return MurmurHash.hash32(key);
    }

}
